home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 September
/
PCWorld_2008-09_cd.bin
/
v cisle
/
sadanastroju
/
autocomplete_manager-2.3-fx.xpi
/
chrome
/
acmanager.jar
/
content
/
acpopup.js
next >
Wrap
Text File
|
2008-03-14
|
48KB
|
1,306 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is the Autocomplete Manager extension.
*
* The Initial Developer of the Original Code is
* Nikitas Liogkas <nikitas@acm.org>.
* Portions created by the Initial Developer are Copyright (C) 2005-2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): James Ravn, Heiwad Osman
* Version: 2.3
*
* ***** END LICENSE BLOCK ***** */
// acpopup.js
// implements the location bar and popup functionality; retrieves all
// matching entries from the filterer and displays them in a custom popup
// that contains a tree with the suggestions ordered by the desired criterion
const acm_atomService = Components.classes["@mozilla.org/atom-service;1"]
.getService(Components.interfaces.nsIAtomService);
const ACM_LOAD_TAB_IN_BACKGROUND = "browser.tabs.loadInBackground";
// popup parameters
var acm_popupExists = false;
var acm_preventPopup = false; // hack to prevent re-display of popup when pressing Shift+Del
var acm_disablePopup = false; // temporarily disable popup at user's will
var acm_disableInlining = false; // prevent inlining after a Del or a Backspace
var acm_typedPrefix = null; // the prefix the user has typed in so far
var acm_tooltip = null; // the tooltip to display on a popup re-sorting
var acm_tooltipTimeout = null; // the id of the tooltip timeout call
var acm_shiftPressed = false;
var acm_ontextenteredHandler = null; // the original handler code that we override
var acm_loadParams = null; // the parameters to pass to handleURLBarCommand()
// suggestion list caching
var acm_lastPrefix = null; // the prefix the user had typed in last time we looked
// "" denotes blank search string / history dropdown
var acm_lastResults = null; // cached suggestion result array for this browser window
// common protocols and prefixes to ignore
const acm_commonProtocol = /^(http|https|ftp):\/\//i;
const acm_commonPrefix = /^(www|ftp)\./i;
// regular expression for non letters, digits, and spaces
const acm_nonLetterDigitSpace = /[^a-z0-9\s]+/gi;
// cached value of the ACM_BOOKMARKS_FIRST preference for better performance when sorting
var acm_bookfirst;
// hint to alphaURLCompareFun to check bookmarks-first ordering when called directly,
// rather than from another sorting function
var acm_checkBookFirst = false;
// popup tree
var acm_treePopup = null;
var acm_popupTreePos = null;
var acm_maxrows; // number of visible rows in the popup tree
var acm_rowHeight = 0; // the height of a single row in the popup tree
var acm_suggestions; // array of AutocompleteCandidate's currently displayed on the popup
//var browser_version_index = navigator.userAgent.indexOf("Firefox") + 8;
//var browser_version = navigator.userAgent.substring(browser_version_index,
// navigator.userAgent.length);
//if (browser_version.charAt(0) !== '1')
// the view for the popup tree
var acm_treePopupView =
{
rowCount: 0,
getCellText: function(row, column)
{
var text = "";
// bookmarks separator
if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length)
return text;
const entry = acm_suggestions[row];
var date = "[" + acm_micros2date(entry.last_visit) + "]";
if (column.id === "colURL")
text = entry.URL;
else if (column.id === "coltitle")
text = entry.title;
else if (column.id === "colsource")
text = date;
return text;
},
// returns the icon URL to display
getImageSrc: function(row, column)
{
// bookmarks separator
if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length)
return null;
const entry = acm_suggestions[row];
if (column.index === 0) {
var iconURL = entry.icon;
if (iconURL !== "") // URL-specific favicon
return iconURL;
else { // generic theme favicon
return acm_defaultIcon;
}
}
else
return null;
},
getRowProperties: function(row, props){ },
getCellProperties: function(row, column, properties)
{
// bookmarks separator
if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length)
return;
// dim the text for all but the first column
if (column.index !== 0)
properties.AppendElement(acm_atomService.getAtom("dim"));
// make the bookmark matches italic
if (acm_suggestions[row].source === ACM_SOURCE_BOOKMARKS)
properties.AppendElement(acm_atomService.getAtom("italic"));
// bold the column where the match occurred
if (acm_getPreference(ACM_BOLD_MATCHING)) {
var field = acm_suggestions[row].matched_against.substr(2);
if (column.id === "col" + field)
properties.AppendElement(acm_atomService.getAtom("bold"));
}
},
getColumnProperties: function(colid, col, props) { },
isContainer: function(index) { return false; },
isEditable: function(row, column) { return false; },
// draw separator after matching bookmarks, if they are being displayed at the top
// NOTE: the sanity checks are done elsewhere for better performance, and
// an extra stub entry is added to acm_suggestions[] after sorting, just for the separator
isSeparator: function(row) {
// do not insert bookmarks separator if either no bookmarks or history entries matched
if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length)
return true;
else
return false;
},
isSorted: function(row) { return false; },
setCellText: function(row, column, value) { },
setTree: function() { }
};
// timer to defer matching when typing rapidly in the location bar
var acm_typingTimer = Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
const acm_timerDelay = 250; // in milliseconds; 50 is the Firefox default
var acm_timerObserver =
{
observe: function(subject, topic, data) {
// invoke a bit later to (maybe) avoid freezing user's typing on the location bar
//setTimeout(acm_handleTyping, 30);
acm_handleTyping();
}
};
// handles a string typed on the location bar by producing the corresponding popup
function acm_handleTyping()
{
// check for difference in common protocols, e.g. transition from 'http:/' to 'http://'
// NOTE: do not use indexOf() for regular expressions
if (acm_lastPrefix !== null
&& acm_lastPrefix.search(acm_commonProtocol) === -1
&& acm_typedPrefix.search(acm_commonProtocol) !== -1)
acm_lastPrefix = null; // invalidate window cache
// check for difference in common prefixes, e.g. transition from 'www' to 'www.'
if (acm_lastPrefix !== null
&& acm_lastPrefix.search(acm_commonPrefix) === -1
&& acm_typedPrefix.search(acm_commonPrefix) !== -1)
acm_lastPrefix = null; // invalidate window cache
// for the blank search string, Firefox uses the order by which entries were entered
// in the history file; we use most recently visited.
if (acm_typedPrefix === "") {
// set the number of matching bookmarks for placing the bookmarks separator on the popup
if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
else
acm_matching_bookmarks = -1;
acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
}
// check if we can reuse the last computed results; no need to re-sort
else if (acm_lastPrefix !== null && acm_lastPrefix !== ""
&& acm_typedPrefix.length >= acm_lastPrefix.length
&& acm_typedPrefix.substring(0, acm_lastPrefix.length) === acm_lastPrefix) {
// remove bookmarks separator, as it will be re-inserted by acm_buildPopup()
if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
acm_lastResults.splice(acm_matching_bookmarks, 1);
acm_lastResults = acm_getFilteredCandidates(acm_lastResults);
acm_buildPopup(acm_lastResults);
}
// perform the matching from scratch
else {
acm_lastResults = acm_getSortedSuggestionList(null);
acm_buildPopup(acm_lastResults);
}
// update prefix
acm_lastPrefix = acm_typedPrefix;
// complete and highlight best match inline, it is matched at the beginning of URL or title
if (acm_getPreference(ACM_INLINE) && acm_popupExists
&& acm_suggestions[0].matched_against.indexOf("b-") === 0 && !acm_disableInlining) {
// strip common protocols and prefixes, unless they were typed in
var field = acm_suggestions[0].matched_against.substr(2);
var new_value = acm_suggestions[0][field];
if (field === "URL") {
if (acm_typedPrefix.search(acm_commonProtocol) === -1)
new_value = new_value.replace(acm_commonProtocol, "");
if (acm_typedPrefix.search(acm_commonPrefix) === -1)
new_value = new_value.replace(acm_commonPrefix, "");
}
const urlbar = document.getElementById("urlbar");
urlbar.value = new_value;
urlbar.setSelectionRange(acm_typedPrefix.length, urlbar.textLength);
}
}
// for debugging output
function acm_console(msg) {
const consoleService = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
consoleService.logStringMessage(msg);
}
// invalidates the caches of all the main browser windows; needed because
// every window maintains its own instances of the acm_* global variables
function acm_invalidateCaches()
{
var browser_windows = acm_mediator.getEnumerator("navigator:browser");
while (browser_windows.hasMoreElements())
browser_windows.getNext().acm_lastPrefix = null;
}
// handles text being typed in the location bar
function acm_urlbarOnInput(event)
{
// do not display popup if Autocomplete is off
if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off")
return;
// popup has been temporarily disabled
if (acm_disablePopup)
return;
// prevent re-display of popup when deleting an entry or for a clickngo middle click on a new tab
if (acm_preventPopup) {
acm_preventPopup = false;
return;
}
// enable inlining if characters have been added to the prefix
const urlbar = document.getElementById("urlbar");
if (acm_typedPrefix !== null && urlbar.value.length > acm_typedPrefix.length && acm_disableInlining)
acm_disableInlining = false;
var lastPrefix = acm_typedPrefix;
acm_typedPrefix = urlbar.value;
if (acm_getPreference(ACM_SHOW_ONARROW) && !acm_popupExists)
return;
// the urlbar is filled with the most recently loaded URL (possible blank) when Esc is pressed on
// the popup in a full-screen Open Web Location dialog;
// restore acm_typedPrefix to its previous value, and do not set acm_popupExists to false,
// since that will be done in the Esc handler
if (!document.getElementById("urlbar-container")) {
var lastURL = acm_prefs.getComplexValue("general.open_location.last_url",
Components.interfaces.nsISupportsString).data;
if (lastPrefix !== null && acm_typedPrefix === lastURL) {
acm_typedPrefix = lastPrefix;
return;
}
}
// hide popup if location bar was cleared
if (acm_typedPrefix === null || acm_typedPrefix === "") {
// || acm_typedPrefix === window._content.document.location.href) {
acm_typingTimer.cancel();
if (acm_popupExists) {
acm_popupExists = false;
const popup = document.getElementById("ACM_Popup");
popup.hidePopup();
}
return;
}
// restart typing timer
acm_typingTimer.cancel();
acm_typingTimer.init(acm_timerObserver, acm_timerDelay, acm_typingTimer.TYPE_ONE_SHOT);
}
// handles keyboard navigation on the location bar and the popup
function acm_urlbarNavigation(event)
{
const urlbar = document.getElementById("urlbar");
const popup = document.getElementById("ACM_Popup");
var changePos;
if (event.altKey) {
var key = event.charCode;
// Alt+L places the focus on the location bar, temporarily disabling the popup
// HACK: for the Open Web Location dialog, the wrong char code is given to us!?
if ( (document.getElementById("urlbar-container") && key === event.DOM_VK_L)
|| (document.getElementById("openLocation") &&key === event.DOM_VK_SEPARATOR) ) {
acm_typingTimer.cancel();
if (acm_popupExists) {
acm_popupExists = false;
popup.hidePopup();
}
urlbar.select();
acm_disablePopup = true;
event.preventDefault();
event.stopPropagation();
return;
}
}
// only for the main location bar
if (event.ctrlKey && document.getElementById("urlbar-container")) {
var key = event.charCode;
// Ctrl+<number> re-sorts the suggestion list according to the corresponding criterion
if (acm_popupExists && key >= event.DOM_VK_0 && key <= event.DOM_VK_9) {
// remove bookmarks separator, as it is going to be re-inserted by acm_buildPopup()
if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
acm_lastResults.splice(acm_matching_bookmarks, 1);
switch (key) {
case event.DOM_VK_1:
acm_showTooltip(0);
acm_buildPopup(acm_lastResults.sort(acm_defaultCompareFun));
break;
case event.DOM_VK_2:
acm_showTooltip(1);
acm_buildPopup(acm_lastResults.sort(acm_topCompareFun));
break;
case event.DOM_VK_3:
acm_showTooltip(2);
acm_buildPopup(acm_lastResults.sort(acm_mfuCompareFun));
break;
case event.DOM_VK_4:
acm_showTooltip(3);
acm_buildPopup(acm_lastResults.sort(acm_mruCompareFun));
break;
case event.DOM_VK_5:
acm_showTooltip(4);
acm_buildPopup(acm_lastResults.sort(acm_alphaURLCompareFun));
break;
case event.DOM_VK_6:
acm_showTooltip(5);
acm_buildPopup(acm_lastResults.sort(acm_alphaTitleCompareFun));
break;
default:
// restore the bookmarks separator, which we removed before re-sorting
if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
acm_lastResults.splice(acm_matching_bookmarks, 0, new AutocompleteCandidate());
break;
}
// prevent other elements from handling this event (necessary for Windows only!)
event.preventDefault();
event.stopPropagation();
return;
}
}
if ( (event.keyCode === event.DOM_VK_TAB) && acm_popupExists ) {
changePos = (event.shiftKey) ? -1 : 1;
// Tab completion; doesn't work yet!
/* if (!event.shiftKey) {
// we are on the location bar; fill in the longest common prefix of all suggestions' URLs
if (acm_popupTreePos === -1) {
var lcp = acm_longestCommonPrefix(acm_suggestions);
}
else { // we are on the popup; filter suggestions based on current location bar value
//for (var i = acm_suggestions.length - 1; i >= 0; i--) {
// if (acm_suggestions[i])
// acm_suggestions.splice(i, 1);
//}
}
}
// undo last action
else {
} */
// prevent other elements from handling this event
event.preventDefault();
event.stopPropagation();
}
// Del or Shift+Del deletes an entry
else if (event.keyCode === event.DOM_VK_DELETE && acm_popupExists
&& acm_popupTreePos > -1) {
// ignore if it's a bookmarks separator
if (acm_popupTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length) {
event.preventDefault();
event.stopPropagation();
return;
}
// if it is a history entry, delete it from the history file
if (acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_HISTORY) {
var uriToDelete = acm_ioService.newURI(acm_suggestions[acm_popupTreePos].URL, null, null);
acm_browserHistory.removePage(uriToDelete);
}
// if it is a bookmark, just remove it from the candidates array
else if (acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_BOOKMARKS)
acm_Aggregator.singleton().removeSingleBookmark(acm_suggestions[acm_popupTreePos].resource,
acm_suggestions[acm_popupTreePos].URL);
// find what the new active suggestion should be after the reconstruction of the popup
var newSelection = (acm_popupTreePos < acm_suggestions.length - 1) ?
acm_popupTreePos : acm_suggestions.length - 2;
if (newSelection === -1) { // no suggestions left, close popup
acm_popupExists = false;
popup.hidePopup();
return;
}
// was a bookmark removed?
var bookmarkRemoved = false;
if (acm_matching_bookmarks > 0
&& acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_BOOKMARKS)
bookmarkRemoved = true;
// remove bookmarks separator, as it is going to be re-inserted by acm_buildPopup()
if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
acm_suggestions.splice(acm_matching_bookmarks, 1);
// decrement acm_matching_bookmarks if needed
if (bookmarkRemoved && acm_matching_bookmarks > 0)
acm_matching_bookmarks--;
// remove selected entry (remember, we might have removed the bookmarks separator already)
acm_popupTreePos = (bookmarkRemoved || acm_matching_bookmarks < 1) ?
acm_popupTreePos : acm_popupTreePos - 1;
acm_suggestions.splice(acm_popupTreePos, 1);
// update cached results and redraw the popup, even if we haven't removed an entry
// NOTE: acm_buildPopup() resets acm_popupTreePos, so we have to restore it for acm_showPopup()
acm_lastResults = acm_suggestions;
acm_buildPopup(acm_lastResults);
acm_popupTreePos = newSelection;
// changing urlbar.value triggers 'oninput' on Windows (and in Firefox 1.0)
// NOTE: always do this after invoking acm_buildPopup()
acm_preventPopup = true;
if (acm_popupTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length)
urlbar.value = acm_typedPrefix;
else
urlbar.value = acm_suggestions[acm_popupTreePos].URL;
return;
}
// Alt+Down, Alt+Up, or F4 toggle the history popup; Ctrl+Up/Down switches tabs
else if (event.keyCode === event.DOM_VK_DOWN) {
if (event.altKey || event.ctrlKey)
return;
// re-enable popup if it has been temporarily disabled
acm_disablePopup = false;
changePos = 1;
}
else if (event.keyCode === event.DOM_VK_UP) {
if (event.altKey || event.ctrlKey)
return;
changePos = -1;
}
// full screen mode; fix for bug 228355
else if (event.keyCode === event.DOM_VK_F11) {
if (acm_popupExists) {
acm_popupExists = false;
popup.hidePopup();
}
return;
}
// close popup for Ctrl+PgUp/PgDown (switching tabs)
else if (event.keyCode === event.DOM_VK_PAGE_DOWN) {
if (event.ctrlKey) {
if (acm_popupExists) {
acm_popupExists = false;
popup.hidePopup();
}
return;
}
else {
if (acm_popupTreePos !== -1)
changePos = acm_maxrows - 1;
// move to first suggestion from location bar
else
changePos = 1;
}
}
else if (event.keyCode === event.DOM_VK_PAGE_UP) {
if (event.ctrlKey) {
if (acm_popupExists) {
acm_popupExists = false;
popup.hidePopup();
}
return;
}
else {
if (acm_popupTreePos !== -1)
changePos = -(acm_maxrows - 1);
// move to last suggestion from location bar
else
changePos = acm_suggestions.length;
}
}
// delete possibly inlined URL, but do not close the popup
// NOTE: the use of Ctrl apparently changes the char codes!?
else if (event.keyCode === event.DOM_VK_DELETE || event.keyCode === event.DOM_VK_BACK_SPACE
|| (event.ctrlKey && event.charCode - 32 === event.DOM_VK_X) ) {
if (acm_popupExists) {
// cursor is on the location bar
if (acm_popupTreePos === -1) {
if (acm_getPreference(ACM_INLINE) && acm_suggestions[0].matched_against.indexOf("b-") === 0
&& !acm_disableInlining) {
// fix for bug 135019
urlbar.value = urlbar.value.substring(0, urlbar.selectionStart);
acm_disableInlining = true;
}
else
urlbar.value = acm_typedPrefix;
}
// cursor is somewhere on the popup; no part of the URL is selected
else {
urlbar.value = acm_suggestions[acm_popupTreePos].URL;
if (acm_getPreference(ACM_INLINE) && !acm_disableInlining) {
acm_disableInlining = true;
acm_typedPrefix = urlbar.value;
}
}
}
// deleting a part of the URL when there is no popup shouldn't trigger inline autocomplete
else {
if (acm_getPreference(ACM_INLINE) && !acm_disableInlining) {
acm_disableInlining = true;
acm_typedPrefix = urlbar.value;
}
}
return;
}
// close the popup, inlining the selected address, but do not load the page
else if (event.keyCode === event.DOM_VK_LEFT
|| event.keyCode === event.DOM_VK_RIGHT
|| event.keyCode === event.DOM_VK_HOME
|| event.keyCode === event.DOM_VK_END) {
// cancel the timer, so that the popup does not appear
acm_typingTimer.cancel();
if (acm_popupExists) {
// cursor is on the location bar
if (acm_popupTreePos === -1) {
if (acm_getPreference(ACM_INLINE) && acm_suggestions[0].matched_against.indexOf("b-") === 0
&& !acm_disableInlining)
urlbar.value = acm_suggestions[0][acm_suggestions[0].matched_against.substr(2)];
else
urlbar.value = acm_typedPrefix;
}
// cursor is somewhere on the popup
else
urlbar.value = acm_suggestions[acm_popupTreePos].URL;
acm_popupExists = false;
popup.hidePopup();
}
return;
}
else if (event.keyCode === event.DOM_VK_ESCAPE) {
// cancel the timer, so that the popup does not appear after Esc is pressed
acm_typingTimer.cancel();
if (acm_popupExists) {
urlbar.value = acm_typedPrefix;
acm_popupExists = false;
popup.hidePopup();
// prevent other elements from handling this event
event.preventDefault();
event.stopPropagation();
}
else {
urlbar.setAttribute("popupOpen", "false");
if (document.getElementById("urlbar-container"))
handleURLBarRevert();
}
// re-enable popup if it has been temporarily disabled
acm_disablePopup = false;
return;
}
// runs after the ontextentered handler
else if (event.keyCode === event.DOM_VK_ENTER || event.keyCode === event.DOM_VK_RETURN) {
// Enter on the bookmarks separator
// NOTE: if the popup is off, acm_matching_bookmarks retains its value from last time
if (acm_popupExists
&& acm_treePopup.view.selection.currentIndex === acm_matching_bookmarks
&& acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
return;
// fix for bug 101497, if text in location bar is not fully selected, e.g., after a Ctrl+L
// NOTE: don't move this down!
if (acm_getPreference(ACM_INLINE) && urlbar.selectionStart !== urlbar.selectionEnd
&& urlbar.selectionStart !== 0)
urlbar.value = acm_suggestions[0].URL;
// cancel the timer, so that the popup does not appear after Enter is pressed
acm_typingTimer.cancel();
// hide the popup
var popupExisted = false;
if (acm_popupExists) {
popupExisted = true;
acm_popupExists = false;
popup.hidePopup();
}
// re-enable popup if it has been temporarily disabled
acm_disablePopup = false;
// non-browser location bar: if the popup was open, inline the URL but do not close the
// dialog, otherwise load the existing URL
if (!document.getElementById("urlbar-container")) {
if (popupExisted) {
event.preventDefault();
event.stopPropagation();
}
return;
}
// load the URL
// NOTE: other extensions (e.g., Tabbrowser Preferences, DeviantLink) may define
// their own custom 'ontextentered' handler, which may itself have a 'param' argument;
// replace all such arguments with 'acm_loadParams' and then invoke the handler
var originalHandler = acm_ontextenteredHandler.replace("param", "acm_loadParams", "g");
// NOTE: no 'return' is allowed in eval()
originalHandler = originalHandler.replace("return ", "");
eval(originalHandler);
return;
}
// return for most other keystrokes
else {
// hide popup on arbitrary keyboard shortcut
if (event.shiftKey || event.ctrlKey || event.altKey
|| (event.keyCode >= event.DOM_VK_F1 && event.keyCode <= event.DOM_VK_F24
&& event.keyCode !== event.DOM_VK_F4) // F4 needs acm_popupExists to be true
&& acm_popupExists) {
// do not close popup for common Shift combinations for special characters
// than can appear on the location bar
var key = event.charCode;
if ( (key >= 33 && key <= 38) || (key >= 40 && key <= 43) || key === 58 || key === 60
|| (key >= 62 && key <= 64) || key === 94 || key === 95 || (key >= 123 && key <= 126) )
return;
// for extra French keyboard special characters
if (key === 46 || key === 47 || (key >= 51 && key <= 56) || key === 163 || key === 176)
return;
// NOTE: The use of Shift apparently changes the char codes!?
key -= 32;
// fix for bug 91536; see https://bugzilla.mozilla.org/show_bug.cgi?id=91536#c4
if ( (key === event.DOM_VK_X && urlbar.selectionStart === 0
&& urlbar.selectionEnd === urlbar.textLength)
|| (key !== event.DOM_VK_A && key !== event.DOM_VK_C && key !== event.DOM_VK_L
&& key !== event.DOM_VK_X && key !== event.DOM_VK_Z) ) {
acm_popupExists = false;
popup.hidePopup();
}
}
return;
}
// return if Autocomplete is off
if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off")
return;
// if there is no popup yet, build and display it;
// this executes only for arrow up/down and Page Up/Down
if (!acm_popupExists) {
acm_typedPrefix = acm_lastPrefix = urlbar.value;
// for the blank search string, Firefox uses the order by which entries were entered
// in the history file; we use most recently visited.
if (acm_typedPrefix === "") {
// set the number of matching bookmarks for placing the bookmarks separator on the popup
if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
else
acm_matching_bookmarks = -1;
acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
}
else {
acm_lastResults = acm_getSortedSuggestionList(null);
acm_buildPopup(acm_lastResults);
}
return;
}
// deselect text in the location bar since we are on the popup
if (acm_getPreference(ACM_INLINE))
urlbar.setSelectionRange(urlbar.textLength, urlbar.textLength);
// update tree selection; -1 indicates we are on the location bar
// NOTE: PgUp/PgDown from location bar is handled in the respective keyboard handlers
// HOWTOFIX: when we Page Down on the popup, the right end of a long URL is displayed
var newTreePos = acm_popupTreePos + changePos;
if (newTreePos < 0) {
// page up on the first page; move to first suggestion
if (acm_popupTreePos >= 1 && acm_popupTreePos <= acm_maxrows - 1)
newTreePos = 0;
// page up from first suggestion; move to location bar
else if (acm_popupTreePos === 0)
newTreePos = -1;
// arrow up from location bar
else
newTreePos = acm_suggestions.length - 1;
}
else if (newTreePos > acm_suggestions.length - 1) {
// page down on the last page; move to last suggestion
if (acm_popupTreePos >= acm_suggestions.length - acm_maxrows - 1
&& acm_popupTreePos <= acm_suggestions.length - 2)
newTreePos = acm_suggestions.length - 1;
// page down from last suggestion; move to location bar
else
newTreePos = -1;
}
// otherwise, do nothing
//else
// ;
// move to new position on tree
if (newTreePos === -1) {
acm_treePopup.view.selection.clearSelection();
acm_treePopup.treeBoxObject.scrollToRow(0);
}
else {
acm_treePopup.view.selection.select(newTreePos);
acm_treePopup.treeBoxObject.ensureRowIsVisible(newTreePos);
}
acm_popupTreePos = newTreePos;
// update location bar value
if ( (newTreePos === -1)
|| (newTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0
&& acm_matching_bookmarks !== acm_suggestions.length) )
urlbar.value = acm_typedPrefix;
else
urlbar.value = acm_suggestions[newTreePos].URL;
}
// re-builds and displays the popup, populated with entries from the argument sorted suggestion list
function acm_buildPopup(sortedList)
{
const urlbar = document.getElementById("urlbar");
const popup = document.getElementById("ACM_Popup");
// if no entries matched, hide popup and return
// NOTE: this is also the case when Autocomplete is turned off at browser startup
if (sortedList.length === 0) {
if (acm_popupExists) {
acm_popupExists = false;
popup.hidePopup();
}
return;
}
// construct the tree for the popup
acm_suggestions = sortedList;
// add a stub entry for the bookmarks separator if at least one bookmark matched
if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST)
&& acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
acm_suggestions.splice(acm_matching_bookmarks, 0, new AutocompleteCandidate());
if (!acm_treePopup)
acm_treePopup = document.getAnonymousElementByAttribute(popup, "anonid", "tree");
else {
// used when deleting a suggestion to re-center the popup
var firstVisibleRow = acm_treePopup.treeBoxObject.getFirstVisibleRow();
acm_treePopup.treeBoxObject.invalidate();
}
acm_treePopupView.rowCount = acm_suggestions.length;
acm_treePopup.treeBoxObject.view = acm_treePopupView;
// NOTE: this doesn't work, so set the 'height' property instead
//acm_treePopup.setAttribute("rows", visible_rows);
// calculate the number of visible rows before restoring acm_maxrows
var visible_rows = (acm_suggestions.length > acm_maxrows) ? acm_maxrows : acm_suggestions.length;
// show and hide the popup once to get the row height
if (!acm_rowHeight) {
popup.showPopup(urlbar, -1, -1, "popup", "bottomleft", "topleft");
acm_rowHeight = acm_treePopup.treeBoxObject.rowHeight;
popup.hidePopup(); // restores acm_maxrows and adds 'keypress' event listener
}
// temporarily disable keyboard tab selection, since it conflicts with on-the-fly re-sorting
// on Windows; will re-enable it when the popup is hidden
// NOTE: this is defined in browser.js, not other places we support (e.g., Open Web Location dialog)
if (document.getElementById("urlbar-container"))
window.removeEventListener("keypress", ctrlNumberTabSelection, false);
// set popup height and scrollbar
acm_treePopup.setAttribute("height", visible_rows * acm_rowHeight);
acm_treePopup.setAttribute("hidescrollbar", acm_suggestions.length <= acm_maxrows);
acm_popupTreePos = -1; // we are on the location bar
// determine column order
// NOTE: do not use columns[], since the tree does not exist yet
var show_titles = acm_getPreference(ACM_SHOW_TITLES);
var swap_columns = acm_getPreference(ACM_SWAP_COLUMNS);
var colURL, colTitle;
if (show_titles && swap_columns) {
// we are in the initial state; swap columns
if (acm_treePopup.firstChild.childNodes[0].getAttribute("id") === "colURL") {
colURL = acm_treePopup.firstChild.childNodes[0];
colTitle = acm_treePopup.firstChild.childNodes[1];
acm_treePopup.firstChild.insertBefore(colTitle, colURL);
}
// columns have been swapped already; leave it that way
else {
colURL = acm_treePopup.firstChild.childNodes[1];
colTitle = acm_treePopup.firstChild.childNodes[0];
}
}
else {
// we are in the initial state; leave it that way
if (acm_treePopup.firstChild.childNodes[0].getAttribute("id") === "colURL") {
colURL = acm_treePopup.firstChild.childNodes[0];
colTitle = acm_treePopup.firstChild.childNodes[1];
}
// restore initial state
else {
colURL = acm_treePopup.firstChild.childNodes[1];
colTitle = acm_treePopup.firstChild.childNodes[0];
acm_treePopup.firstChild.insertBefore(colURL, colTitle);
}
}
// determine the visible columns and their crop modes
colURL.setAttribute("crop", acm_getPreference(ACM_ADDRESS_TRUNC));
if (show_titles) {
colTitle.setAttribute("crop", acm_getPreference(ACM_TITLE_TRUNC));
colTitle.setAttribute("hidden", "false");
}
else
colTitle.setAttribute("hidden", "true");
var colSource = acm_treePopup.firstChild.childNodes[2];
if (acm_getPreference(ACM_SHOW_DATE))
colSource.setAttribute("hidden", "false");
else
colSource.setAttribute("hidden", "true");
// set column widths heuristically
// NOTE: if these are set in acpopup.xml, they don't adapt dynamically to a column swap
colURL.setAttribute("flex", 12);
colTitle.setAttribute("flex", 6);
colSource.setAttribute("flex", 5);
// show popup; workaround for bug 348146 that only appears on Mac OS X
// NOTE: set this to 'true' to make inline autocompletion work the first time the popup
// appears, and also so that the popup is not shown after buttons have been pressed
acm_popupExists = true;
setTimeout(acm_showPopup, 0, firstVisibleRow);
}
// shows the popup; firstVisibleRow is used when deleting a suggestion to re-center the popup
function acm_showPopup(firstVisibleRow)
{
// needed due to the async nature (setTimeout() call) of the popup creation
if (!acm_popupExists)
return;
const urlbar = document.getElementById("urlbar");
const popup = document.getElementById("ACM_Popup");
popup.showPopup(urlbar, -1, -1, "popup", "bottomleft", "topleft");
if (acm_popupTreePos === -1) { // new popup
acm_treePopup.view.selection.select(-1);
acm_treePopup.treeBoxObject.scrollToRow(0);
}
else { // after deleting an entry
acm_treePopup.view.selection.select(acm_popupTreePos);
// NOTE: acm_treePopup.treeBoxObject.getLastVisibleRow() returns acm_suggestions.length + 1
// on the last popup page (if that page is not also the first)!?
if (acm_popupTreePos >= acm_suggestions.length - acm_maxrows + 1
&& acm_popupTreePos <= acm_suggestions.length - 1
&& firstVisibleRow !== 0)
acm_treePopup.treeBoxObject.scrollToRow(firstVisibleRow - 1);
else
acm_treePopup.treeBoxObject.scrollToRow(firstVisibleRow);
}
}
// shows the urlbar tooltip
function acm_showTooltip(tooltipIndex)
{
// another tooltip is being displayed
if (acm_tooltipTimeout !== null) {
acm_tooltip.setAttribute("hidden", true);
clearTimeout(acm_tooltipTimeout);
}
// the tooltip labels are stored in a deck
acm_tooltip = document.getElementById("acm_resorting-deck").childNodes.item(tooltipIndex);
acm_tooltip.setAttribute("hidden", false);
acm_tooltipTimeout = setTimeout(acm_hideTooltip, 3000);
}
// hides the urlbar tooltip (after a timeout set in acm_showTooltip())
function acm_hideTooltip()
{
acm_tooltip.setAttribute("hidden", true);
acm_tooltipTimeout = null;
}
// toggles history dropdown; triggered by Alt+Up, Alt+Down, or F4 on the location bar.
// Firefox uses the order by which entries were entered in the history file,
// we use most recently visited.
function acm_toggleHistoryPopup()
{
// return if Autocomplete is off
if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off")
return;
if (!acm_popupExists) {
if (acm_typingTimer)
acm_typingTimer.cancel();
// is restored to its regular value on 'popuphidden'
acm_maxrows = document.getElementById("urlbar").maxDropMarkerRows;
// set the number of matching bookmarks for placing the bookmarks separator on the popup
if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
else
acm_matching_bookmarks = -1;
acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
acm_lastPrefix = ""; // invalidate window cache
}
else {
acm_popupExists = false;
const popup = document.getElementById("ACM_Popup");
popup.hidePopup();
}
}
// handles mouse clicks on popup items
// NOTE: mouse selection/navigation happens separately from keyboard selection/navigation
function acm_popupMouseClick(event, popup)
{
// mouse click on the bookmarks separator
if (acm_treePopup.view.selection.currentIndex === acm_matching_bookmarks
&& acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
return;
// on the browser location bar
if (document.getElementById("urlbar-container")) {
// left or right mouse button
if (event.button === 0 || event.button === 2)
acm_popupItemSelect(event, popup, true);
// middle mouse button opens a new tab; fix for bug 295498
else if (event.button === 1) {
var new_url = acm_suggestions[acm_treePopup.view.selection.currentIndex].URL;
acm_popupExists = false;
popup.hidePopup();
// open tab in foreground or background, based on user preference
var back = acm_getPreference(ACM_LOAD_TAB_IN_BACKGROUND);
var newtab = getBrowser().addTab(new_url);
if (!back)
getBrowser().selectedTab = newtab;
}
}
// on a dialog's location bar
else
acm_popupItemSelect(event, popup, false);
}
// handles selection of a popup item
function acm_popupItemSelect(event, popup, loadURL)
{
var new_url = acm_suggestions[acm_treePopup.view.selection.currentIndex].URL;
if (loadURL)
window._content.document.location.href = new_url;
else
document.getElementById("urlbar").value = new_url;
acm_popupExists = false;
popup.hidePopup();
}
// handles mouse-over events for popup items
function acm_popupMouseMove(event)
{
// update the tree selection
acm_popupTreePos = acm_treePopup.view.selection.currentIndex;
}
// prevents the popup from appearing after a clickngo middle mouse click on a new tab
function acm_urlbarMouseUp(event)
{
// only for the browser location bar
if (document.getElementById("urlbar-container") && event.button === 1)
acm_preventPopup = true;
}
// opens a URL from a non-browser's location bar; code taken from
// chrome://content/browser/openLocation.js::open()
function acm_loadURL()
{
if ("arguments" in window && window.arguments.length >= 1)
browser = window.arguments[0];
var url;
var postData = {};
if (browser)
url = browser.getShortcutOrURI(document.getElementById("urlbar").value, postData);
else
url = document.getElementById("urlbar").value;
try {
// Whichever target we use for the load, we allow third-party services to
// fixup the URI
switch (document.getElementById("openWhereList").value) {
case "0":
browser.loadURI(url, null, postData.value, true);
break;
case "1":
window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
url, postData.value, null, null, true);
break;
case "3":
if (browser.getBrowser && browser.getBrowser().localName == "tabbrowser")
browser.delayedOpenTab(url, null, null, postData.value, true);
else
browser.loadURI(url, null, postData.value, true); // Just do a normal load.
break;
}
}
catch(exception) {
}
if (acm_prefs) {
var str = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
str.data = document.getElementById("urlbar").value;
acm_prefs.setComplexValue("general.open_location.last_url",
Components.interfaces.nsISupportsString, str);
acm_prefs.setIntPref("general.open_location.last_window_choice",
document.getElementById("openWhereList").value);
}
// Delay closing slightly to avoid timing bug on Linux.
window.close();
return false;
}
// matches the user-typed prefix against the provided list of AutocompleteCandidate's and sorts the results
function acm_getSortedSuggestionList(list)
{
acm_bookfirst = acm_getPreference(ACM_BOOKMARKS_FIRST);
if (acm_getPreference(ACM_SORTBY) === "default")
return acm_getFilteredCandidates(list).sort(acm_defaultCompareFun);
else if (acm_getPreference(ACM_SORTBY) === "top")
return acm_getFilteredCandidates(list).sort(acm_topCompareFun);
else if (acm_getPreference(ACM_SORTBY) === "mfu")
return acm_getFilteredCandidates(list).sort(acm_mfuCompareFun);
else if (acm_getPreference(ACM_SORTBY) === "mru")
return acm_getFilteredCandidates(list).sort(acm_mruCompareFun);
else if (acm_getPreference(ACM_SORTBY) === "alpha_address") {
acm_checkBookFirst = true;
var matches = acm_getFilteredCandidates(list).sort(acm_alphaURLCompareFun);
acm_checkBookFirst = false;
return matches;
}
else if (acm_getPreference(ACM_SORTBY) === "alpha_title")
return acm_getFilteredCandidates(list).sort(acm_alphaTitleCompareFun);
else // fallback on error
return acm_getFilteredCandidates(list).sort(acm_defaultCompareFun);
}
// Firefox default ordering
// - if the URL ends with a slash (Web path) or if it has been manually typed,
// add 5 to its visit count
// - sort in order of decreasing visit count
// - on a visit count tie, place Web paths first
// - if both addresses represent Web paths, sort in ascending alphabetical order,
// after ignoring common protocols and prefixes
// The order calculated by this function is not exactly the order Firefox would
// end up with, since an extension currently has no way of finding out whether
// an address has been manually typed.
function acm_defaultCompareFun(candidateA, candidateB)
{
if (acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
var AisWebpath = candidateA.webpath;
var BisWebpath = candidateB.webpath;
var countA = candidateA.visit_count;
if (AisWebpath)
countA += 5;
var countB = candidateB.visit_count;
if (BisWebpath)
countB += 5;
if (countA > countB) // place A before B
return -1;
else if (countA < countB)
return 1;
else {
if (AisWebpath && !BisWebpath)
return -1;
else if (!AisWebpath && BisWebpath)
return 1;
else
return acm_alphaURLCompareFun(candidateA, candidateB);
}
}
// place domain addresses first sorting them in ascending alphabetical order;
// sort the rest also in ascending alphabetical order
function acm_topCompareFun(candidateA, candidateB)
{
if (acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
var AisDomain = candidateA.domain;
var BisDomain = candidateB.domain;
if (AisDomain && !BisDomain) // place A before B
return -1;
else if (!AisDomain && BisDomain)
return 1;
else
return acm_alphaURLCompareFun(candidateA, candidateB);
}
// sort in order of decreasing visit count,
// or in the case of a tie, in ascending alphabetical order
function acm_mfuCompareFun(candidateA, candidateB)
{
if (acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
var countA = candidateA.visit_count;
var countB = candidateB.visit_count;
if (countA > countB) // place A before B
return -1;
else if (countA < countB)
return 1;
else
return acm_alphaURLCompareFun(candidateA, candidateB);
}
// sort in order of decreasing last visit date,
// or in the case of a tie, in ascending alphabetical order
function acm_mruCompareFun(candidateA, candidateB)
{
if (acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
var visitA = candidateA.last_visit;
var visitB = candidateB.last_visit;
if (visitA > visitB) // place A before B
return -1;
else if (visitA < visitB)
return 1;
else
return acm_alphaURLCompareFun(candidateA, candidateB);
}
// sort in ascending alphabetical order by title
function acm_alphaTitleCompareFun(candidateA, candidateB)
{
if (acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
// manufacture appropriate objects to pass as arguments
var candA = new Object();
candA.strippedURL = candidateA.strippedTitle;
candA.URL = "";
var candB = new Object();
candB.strippedURL = candidateB.strippedTitle;
candB.URL = "";
return acm_alphaURLCompareFun(candA, candB);
}
// sort in ascending alphabetical order by address, after ignoring common protocols and prefixes
function acm_alphaURLCompareFun(candidateA, candidateB)
{
if (acm_checkBookFirst && acm_bookfirst) {
if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
return -1;
else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
return 1;
}
var urlA = candidateA.strippedURL;
var urlB = candidateB.strippedURL;
if (urlA < urlB) // place A before B
return -1;
else if (urlA > urlB)
return 1;
// sort http://xyz.com before http://www.xyz.com
else if (candidateA.URL.length < candidateB.URL.length)
return -1;
else if (candidateA.URL.length > candidateB.URL.length)
return 1;
else
return 0;
}